using System;
using System.IO;

namespace NetduinoRobot.Common
{
    /// <summary>
    /// This class implements read and write operations for remote commands.
    /// Read and Write methods contains special handling for serial port specials.
    /// Written by: Salvador Richter
    /// Appache License Version 2.0 
    /// </summary>
    public class CommandReadWrite
    {
        private ISerialPortWrapper _serialPortWraper = null;
        private CommandHelper _commandHelper = null;

        /// <summary>
        /// Constructor for read write handler
        /// Throws Exception if port cannot be opened
        /// </summary>
        /// <param name="wrapper">wrapper item for a real SerialPort instance</param>
        public CommandReadWrite(ISerialPortWrapper wrapper)
        {
            _serialPortWraper = wrapper;
            _commandHelper = new CommandHelper();
            _serialPortWraper.Open();
        }

        /// <summary>
        /// Reads a command from SerialPort, the command needs to be packed in a special envelope, see CommandHelper
        /// Example: <cmd len=00046>DisplayMessage;Haaalllooo</cmd>
        /// len is total length, including start and end tag and has to be 5 digits
        /// Method call is blocking until full command is read from SerialPort
        /// </summary>
        /// <returns>Command read from SerialPort</returns>
        public String ReadXmlCommand()
        {
            byte[] singleBuffer = new byte[1];
            int initialReadSkip = 8; //cmd len=
            int initialReadLength = 14; //cmd len=00000>
            int valueLength = 5;
            int cmdEndTagLength = 6; //</cmd>
            byte[] initialReadBuffer = new byte[initialReadLength];
            byte[] cmdBuffer;
            string s;
            int cmdLength = 0;
            int readLength = 0;

            if (!_serialPortWraper.IsOpen())
            {
                _serialPortWraper.Open();
            }
            // find start tag, ascii representation 60 is '<'
            _serialPortWraper.Read(singleBuffer, 0, 1);
            while (singleBuffer[0] != 60)
            {
                System.Threading.Thread.Sleep(2);
                _serialPortWraper.Read(singleBuffer, 0, 1);
            }
            // wait until at least start tag is ready to be read
            while (_serialPortWraper.BytesToRead() < initialReadLength)
            {
                System.Threading.Thread.Sleep(2);
            }
            // read start tag
            // offset ist im empfangspuffer nicht im port !
            _serialPortWraper.Read(initialReadBuffer, 0, initialReadLength);
            // cut out len param by getchar parameters
            s = new String(System.Text.UTF8Encoding.UTF8.GetChars(initialReadBuffer, initialReadSkip, valueLength));
            cmdLength = int.Parse(s);
            // calculate number of bytes to be read incl end tag </cmd>
            // +1 required because initialReadLength does not include the starting <
            readLength = cmdLength - (initialReadLength + 1);

            cmdBuffer = new byte[readLength];
            // wait until cmd ready to be read
            while (_serialPortWraper.BytesToRead() < readLength)
            {
                System.Threading.Thread.Sleep(2);
            }
            // read cmd incl end tag
            _serialPortWraper.Read(cmdBuffer, 0, readLength);
            // cut off end tag
            s = new String(System.Text.UTF8Encoding.UTF8.GetChars(cmdBuffer, 0, readLength - cmdEndTagLength));
            return s;
        }

        /// <summary>
        /// Writes a command to SerialPort
        /// Command is wrapped into a special envelope, see CommandHelper
        /// Example: <cmd len=00046>DisplayMessage;Haaalllooo</cmd>
        /// len is total length, including start and end tag and has to be 5 digits
        /// </summary>
        /// <param name="command">the command to be send on SerialPort</returns>
        public void WriteXmlCommand(string command)
        {
            if (!_serialPortWraper.IsOpen())
            {
                _serialPortWraper.Open();
            }

            string s = _commandHelper.CreateEnvelope(command);
            _serialPortWraper.Write(s);
        }
    }
}
